#include "CalcSceneViewTool.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
#include "SceneView.h"
#include "GameFramework/PlayerController.h"
#include "Engine/Canvas.h"
#include "EngineModule.h"
#include "SceneView.h"
#include "SceneViewExtension.h"
#include "GameFramework/WorldSettings.h"
#include "Engine/Engine.h"
#include "Engine/LocalPlayer.h"


FCalcSceneViewTool::FCalcSceneViewTool(FViewport* inViewport)
	: shotWorld(nullptr)
	, PlayerController(nullptr)
	, viewport(inViewport)
	, AspectRatioAxisConstraint(EAspectRatioAxisConstraint::AspectRatio_MaintainXFOV)
	,Origin(0.0f,0.0f), Size(1.0f,1.0f)
{


}

bool FCalcSceneViewTool::InitCalcScene(const FScreenshotInfo& info)
{
	screenshotInfo = info;
	check(screenshotInfo.worldTargetActor)
	shotWorld = screenshotInfo.worldTargetActor->GetWorld();
	check(shotWorld);
	PlayerController = shotWorld->GetFirstPlayerController();
	const ERHIFeatureLevel::Type FeatureLevel = shotWorld ? shotWorld->GetFeatureLevel() : GMaxRHIFeatureLevel;
	ViewState.Allocate(FeatureLevel);

	//目前不实现立体渲染 by zhongzi 2020.7.6
	//if (GEngine->StereoRenderingDevice.IsValid())
	//{
	//	const int32 NumViews = GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(true);
	//	check(NumViews > 0);
	//	// ViewState is used for eSSP_LEFT_EYE, so we don't create one for that here.
	//	StereoViewStates.SetNum(NumViews - 1);
	//	for (auto& State : StereoViewStates)
	//	{
	//		State.Allocate();
	//	}
	//}

	return true;
}
FCalcSceneViewTool::~FCalcSceneViewTool()
{
	ViewState.Destroy();
}


FSceneView* FCalcSceneViewTool::CalcSceneView(class FSceneViewFamily* ViewFamily,
	FVector& OutViewLocation,
	FRotator& OutViewRotation,
	FViewport* Viewport,
	class FViewElementDrawer* ViewDrawer)
{


	FSceneViewInitOptions ViewInitOptions;

	if (!CalcSceneViewInitOptions(ViewInitOptions, Viewport, ViewDrawer, EStereoscopicPass::eSSP_FULL))
	{
		return nullptr;
	}

	// Get the viewpoint...technically doing this twice
	// but it makes GetProjectionData better
	FMinimalViewInfo ViewInfo = screenshotInfo.minimalViewInfo;

	ViewInitOptions.ViewLocation = ViewInfo.Location;
	ViewInitOptions.ViewRotation = ViewInfo.Rotation;
	ViewInitOptions.bUseFieldOfViewForLOD = ViewInfo.bUseFieldOfViewForLOD;
	ViewInitOptions.FOV = ViewInfo.FOV;
	ViewInitOptions.DesiredFOV = ViewInfo.DesiredFOV;
	// Fill out the rest of the view init options
	ViewInitOptions.ViewFamily = ViewFamily;

	if (!PlayerController->bRenderPrimitiveComponents)
	{
		// Emplaces an empty show only primitive list.
		ViewInitOptions.ShowOnlyPrimitives.Emplace();
	}
	else
	{
		QUICK_SCOPE_CYCLE_COUNTER(STAT_BuildHiddenComponentList);
		PlayerController->BuildHiddenComponentList(ViewInfo.Location, /*out*/ ViewInitOptions.HiddenPrimitives);
	}

	//@TODO: SPLITSCREEN: This call will have an issue with splitscreen, as the show flags are shared across the view family
	EngineShowFlagOrthographicOverride(ViewInitOptions.IsPerspectiveProjection(), ViewFamily->EngineShowFlags);
	FIntRect rect = FIntRect(0, 0, screenshotInfo.outImageInfo.width, screenshotInfo.outImageInfo.height);
	ViewInitOptions.SetViewRectangle(rect);

	FSceneView* const View = new FSceneView(ViewInitOptions);

	OutViewLocation = View->ViewLocation;
	OutViewRotation = View->ViewRotation;
	// Pass on the previous view transform from the view info (probably provided by the camera if set)
	View->PreviousViewTransform = ViewInfo.PreviousViewTransform;

	ViewFamily->Views.Add(View);

	{
		QUICK_SCOPE_CYCLE_COUNTER(STAT_PostprocessSettings);
		CSV_SCOPED_TIMING_STAT_EXCLUSIVE(PostProcessSettings);
		View->StartFinalPostprocessSettings(ViewInfo.Location);

		TArray<FPostProcessSettings> const* CameraAnimPPSettings = nullptr;
		TArray<float> const* CameraAnimPPBlendWeights = nullptr;
		TArray<EViewTargetBlendOrder> const* CameraAnimPPBlendOrders = nullptr;

		// Base overrides (post process volumes, etc)
		if (PlayerController->PlayerCameraManager)
		{
			PlayerController->PlayerCameraManager->GetCachedPostProcessBlends(CameraAnimPPSettings, CameraAnimPPBlendWeights, CameraAnimPPBlendOrders);

			for (int32 PPIdx = 0; PPIdx < CameraAnimPPBlendWeights->Num(); ++PPIdx)
			{
				if ((*CameraAnimPPBlendOrders)[PPIdx] == VTBlendOrder_Base)
				{
					View->OverridePostProcessSettings((*CameraAnimPPSettings)[PPIdx], (*CameraAnimPPBlendWeights)[PPIdx]);
				}
			}
		}
	
		// Main camera
		View->OverridePostProcessSettings(ViewInfo.PostProcessSettings, ViewInfo.PostProcessBlendWeight);

		// Camera overrides (cameras blending in, camera modifiers, etc)
		if (PlayerController->PlayerCameraManager &&
			CameraAnimPPSettings && CameraAnimPPBlendWeights && CameraAnimPPBlendOrders)
		{
			for (int32 PPIdx = 0; PPIdx < CameraAnimPPBlendWeights->Num(); ++PPIdx)
			{
				if ((*CameraAnimPPBlendOrders)[PPIdx] == VTBlendOrder_Override)
				{
					View->OverridePostProcessSettings((*CameraAnimPPSettings)[PPIdx], (*CameraAnimPPBlendWeights)[PPIdx]);
				}
			}

			PlayerController->PlayerCameraManager->UpdatePhotographyPostProcessing(View->FinalPostProcessSettings);
		}

		View->EndFinalPostprocessSettings(ViewInitOptions);
	}

	for (int ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ViewExt++)
	{
		ViewFamily->ViewExtensions[ViewExt]->SetupView(*ViewFamily, *View);
	}

	return View;
}



bool FCalcSceneViewTool::CalcSceneViewInitOptions(
	struct FSceneViewInitOptions& ViewInitOptions,
	FViewport* Viewport,
	class FViewElementDrawer* ViewDrawer,
	EStereoscopicPass StereoPass)
{
	
	if ((PlayerController == NULL) || (Size.X <= 0.f) || (Size.Y <= 0.f) || (Viewport == NULL))
	{
		return false;
	}
	// get the projection data
	if (GetProjectionData(Viewport, StereoPass, /*inout*/ ViewInitOptions) == false)
	{
		// Return NULL if this we didn't get back the info we needed
		return false;
	}

	// return if we have an invalid view rect
	if (!ViewInitOptions.IsValidViewRectangle())
	{
		return false;
	}

	if (PlayerController->PlayerCameraManager != NULL)
	{
		// Apply screen fade effect to screen.
		if (PlayerController->PlayerCameraManager->bEnableFading)
		{
			ViewInitOptions.OverlayColor = PlayerController->PlayerCameraManager->FadeColor;
			ViewInitOptions.OverlayColor.A = FMath::Clamp(PlayerController->PlayerCameraManager->FadeAmount, 0.0f, 1.0f);
		}

		// Do color scaling if desired.
		if (PlayerController->PlayerCameraManager->bEnableColorScaling)
		{
			ViewInitOptions.ColorScale = FLinearColor(
				PlayerController->PlayerCameraManager->ColorScale.X,
				PlayerController->PlayerCameraManager->ColorScale.Y,
				PlayerController->PlayerCameraManager->ColorScale.Z
			);
		}

		// Was there a camera cut this frame?
		ViewInitOptions.bInCameraCut = PlayerController->PlayerCameraManager->bGameCameraCutThisFrame;
	}

	check(PlayerController && PlayerController->GetWorld());

	{
		////这里是开始全局光效果的	需要正确的 FSceneViewStateReference();
	//ViewState = FSceneViewStateReference();
	//if (ViewState.GetReference() == nullptr)
	//{		
	//	const ERHIFeatureLevel::Type FeatureLevel = shotWorld ? shotWorld->GetFeatureLevel() : GMaxRHIFeatureLevel;
	//	ViewState.Allocate(FeatureLevel);
	//}

	////这里是开始全局光效果的
	//ViewInitOptions.SceneViewStateInterface = ViewState.GetReference();
	 }


	ViewInitOptions.ViewActor = screenshotInfo.worldTargetActor;
	ViewInitOptions.PlayerIndex = 0;
	ViewInitOptions.ViewElementDrawer = ViewDrawer;
	ViewInitOptions.BackgroundColor = FLinearColor::Black;
	ViewInitOptions.LODDistanceFactor = PlayerController->LocalPlayerCachedLODDistanceFactor;
	ViewInitOptions.StereoPass = StereoPass;
	ViewInitOptions.WorldToMetersScale = PlayerController->GetWorldSettings()->WorldToMeters;
	ViewInitOptions.CursorPos = Viewport->HasMouseCapture() ? FIntPoint(-1, -1) : FIntPoint(Viewport->GetMouseX(), Viewport->GetMouseY());
	ViewInitOptions.OriginOffsetThisFrame = shotWorld->OriginOffsetThisFrame;

	return true;
}


bool  FCalcSceneViewTool::GetProjectionData(
	FViewport* Viewport
	, EStereoscopicPass StereoPass
	, FSceneViewProjectionData& ProjectionData) const
{
	// If the actor
	if ((Viewport == NULL) || (PlayerController == NULL) || (Viewport->GetSizeXY().X == 0) || (Viewport->GetSizeXY().Y == 0))
	{
		return false;
	}

	int32 X = FMath::TruncToInt(Origin.X * Viewport->GetSizeXY().X);
	int32 Y = FMath::TruncToInt(Origin.Y * Viewport->GetSizeXY().Y);

	X += Viewport->GetInitialPositionXY().X;
	Y += Viewport->GetInitialPositionXY().Y;

	uint32 SizeX = FMath::TruncToInt(Size.X * Viewport->GetSizeXY().X);
	uint32 SizeY = FMath::TruncToInt(Size.Y * Viewport->GetSizeXY().Y);

	FIntRect UnconstrainedRectangle = FIntRect(X, Y, X + SizeX, Y + SizeY);

	ProjectionData.SetViewRectangle(UnconstrainedRectangle);

	// Get the viewpoint.
	FMinimalViewInfo ViewInfo = screenshotInfo.minimalViewInfo;


	FVector StereoViewLocation = ViewInfo.Location;

	// Create the view matrix
	ProjectionData.ViewOrigin = StereoViewLocation;
	ProjectionData.ViewRotationMatrix = FInverseRotationMatrix(ViewInfo.Rotation) * FMatrix(
		FPlane(0, 0, 1, 0),
		FPlane(1, 0, 0, 0),
		FPlane(0, 1, 0, 0),
		FPlane(0, 0, 0, 1));


	// Create the projection matrix (and possibly constrain the view rectangle)
	FMinimalViewInfo::CalculateProjectionMatrixGivenView(ViewInfo, AspectRatioAxisConstraint, viewport, /*inout*/ ProjectionData);

	for (auto& ViewExt : GEngine->ViewExtensions->GatherActiveExtensions())
	{
		ViewExt->SetupViewProjectionMatrix(ProjectionData);
	};


	return true;
}




void FCalcSceneViewTool::BuildComponentList(const TArray<AActor*> actorlist,TSet<FPrimitiveComponentId>& HiddenComponentsOut)
{

	for (int32 ActorIndex = 0; ActorIndex < actorlist.Num(); ++ActorIndex)
	{
		AActor* HiddenActor = actorlist[ActorIndex];
		if (HiddenActor != NULL)
		{
			TInlineComponentArray<UPrimitiveComponent*> Components;
			HiddenActor->GetComponents(Components);

			for (int32 ComponentIndex = 0; ComponentIndex < Components.Num(); ComponentIndex++)
			{
				UPrimitiveComponent* PrimitiveComponent = Components[ComponentIndex];
				if (PrimitiveComponent->IsRegistered())
				{
					HiddenComponentsOut.Add(PrimitiveComponent->ComponentId);

					for (USceneComponent* AttachedChild : PrimitiveComponent->GetAttachChildren())
					{
						UPrimitiveComponent* AttachChildPC = Cast<UPrimitiveComponent>(AttachedChild);
						if (AttachChildPC && AttachChildPC->IsRegistered())
						{
							HiddenComponentsOut.Add(AttachChildPC->ComponentId);
						}
					}
				}
			}
		}
	}
}
